<template>
  <div class="absolute left-0 right-0 top-0 bottom-[30px] !m-0 p-1">
    <el-container class="h-full pb-2">
      <el-aside>
        <el-scrollbar>
          <div class="flex mt-1 ml-1 mr-[8px]">
            <el-input v-model="searchName" placeholder="搜索会话" clearable />
            <el-button
              type="primary"
              class="ml-1"
              plain
              :icon="useRenderIcon('ic:baseline-plus')"
              @click="handleCreateConversation"
            />
          </div>
          <el-alert
            v-show="isDisconnected"
            title="连接断开 请刷新"
            type="error"
            class="!m-1 max-w-[calc(100%-12px)]"
            :closable="false"
          />
          <div
            v-show="conversationList.length === 0"
            class="mt-2 text-sm text-gray-500 text-center"
          >
            会话列表为空
          </div>
          <ConversationView
            v-for="c in searchConversationList"
            :id="c.targetId"
            :key="c.targetId"
            :name="showTarName(c.targetId)"
            :avatar="showTarAvatar(c.targetId)"
            :selected="c.targetId === globalToId"
            :latestMsg="c.latestMsg"
            :unRead="c.unread"
            :top="c.top"
            @onClick="handleSelectConversation"
            @remove="handleRemoveConversation"
            @top="handleTopConversation"
          />
        </el-scrollbar>
      </el-aside>
      <div class="w-[1px] h-full bg-gray-300" />
      <el-container v-show="globalToId !== 0">
        <el-header height="50px" class="flex items-center !p-2">
          <div class="flex items-center">
            <el-avatar
              shape="square"
              :size="30"
              :src="showTarAvatar(globalToId)"
              class="mr-2"
            />
            <div>{{ showTarName(globalToId) }}</div>
            <span
              class="text-[10px] ml-2 filter drop-shadow-xs"
              :class="[onlineStatus ? 'text-green-500' : 'text-gray-400']"
              >{{ onlineStatus ? '● 在线' : '○ 离线' }}</span
            >
          </div>
        </el-header>
        <hr class="text-gray-300" />
        <el-main class="!p-2 !pr-0">
          <el-scrollbar
            ref="refMsgScroll"
            wrap-style="padding:0 1rem 0 0;"
            @scroll="handleMsgsScroll"
          >
            <div v-show="isLoadingMore || isFirstMsg" class="text-center">
              <span class="text-gray-500 text-xs">{{
                isLoadingMore ? '加载中...' : '已经到顶了'
              }}</span>
            </div>
            <MsgView
              v-for="(msg, k) in msgList"
              :key="k"
              class="msg-view"
              :msg="msg"
              :avatar="showTarAvatar(msg.fromId)"
            />
          </el-scrollbar>
        </el-main>
        <hr class="text-gray-300" />
        <el-footer height="20%" class="bg-white !p-2">
          <div class="h-full relative">
            <div class="flex">
              <el-popover
                ref="refEmoji"
                popper-class="!p-0"
                :show-arrow="false"
                placement="top-start"
                :hide-after="10"
                :width="0"
                trigger="click"
              >
                <template #reference>
                  <el-button text :icon="useRenderIcon('mdi:emoji')" />
                </template>
                <template #default>
                  <EmojiView @select="handleSelectEmoji" />
                </template>
              </el-popover>
              <el-upload
                :show-file-list="false"
                :http-request="handleUploadFile"
                :onSuccess="handleSendFile"
              >
                <template #trigger>
                  <el-button text :icon="useRenderIcon('mdi:file')" />
                </template>
              </el-upload>
            </div>
            <el-input
              v-model="inputMsg"
              type="textarea"
              resize="none"
              input-style="border:0;box-shadow:none;padding:5px 0;"
              :rows="3"
            />
            <div class="absolute right-0 bottom-0 flex">
              <el-button
                type="primary"
                :icon="useRenderIcon('mdi:send')"
                @click="handleSendMsg"
              />
            </div>
          </div>
        </el-footer>
      </el-container>
    </el-container>
  </div>
</template>

<script setup lang="tsx">
import { useRenderIcon } from '@/components/ReIcon/src/hooks'
import MsgView from './MsgView.vue'
import { getToken } from '@/utils/auth'
import {
  Conversation,
  ConversationBody,
  EventCode,
  FileBody,
  HistoryBody,
  Msg,
  MsgType,
  SubscribeBody,
  SystemBody,
  TextBody
} from './types'
import {
  computed,
  nextTick,
  onMounted,
  onUnmounted,
  Ref,
  ref,
  unref
} from 'vue'
import { getOnUser, getUserList, UserRes } from '@/api/user'
import { HttpResultCode } from '@/utils/http/enums'
import ConversationView from './ConversationView.vue'
import { buildMsg, ownerId, refreshData } from './utils'
import {
  PopoverInstance,
  ScrollbarInstance,
  UploadRawFile,
  UploadRequestOptions
} from 'element-plus'
import { compressImage } from '@/utils/image'
import { UploadData, uploadFile, UploadRes } from '@/api/file'
import { debounce } from '@pureadmin/utils'
import { message } from '@/utils/message'
import { addDialog, closeDialog } from '@/components/ReDialog'
import SelectUserView from './SelectUserView.vue'
import EmojiView from './EmojiView.vue'

const onlineStatus = ref(false)

const refEmoji = ref<PopoverInstance>()
const refMsgScroll = ref<ScrollbarInstance>()
const userList = ref<UserRes[]>([])
let socket = null
const conversationList: Ref<Conversation<any>[]> = ref([])
const msgList: Ref<Msg<any>[]> = ref([])
const inputMsg = ref('')

const searchName = ref('')

const searchConversationList = computed(() => {
  return conversationList.value.filter(c => {
    const user = userList.value.find(u => u.id === c.targetId)
    return user?.nickname?.includes(searchName.value) || false
  })
})

function commonSend(msg: Msg<any>) {
  socket.send(JSON.stringify(msg))
  msgList.value.push(msg)
  conversationList.value = conversationList.value.map(c => {
    if (c.targetId === globalToId.value) {
      c.latestMsg = msg
    }
    return c
  })
  sortConversationList(conversationList.value)
  nextTick().then(() => {
    if (refMsgScroll.value) {
      refMsgScroll.value.setScrollTop(999999)
    }
  })
}
const handleSendMsg = () => {
  if (!inputMsg.value) return
  const msg = buildMsg<TextBody>(globalToId.value, MsgType.Text, {
    content: inputMsg.value
  })
  inputMsg.value = ''
  commonSend(msg)
}

function loadMore() {
  if (isLoadingMore.value || isFirstMsg.value) return
  isLoadingMore.value = true
  dbLoadMore()
}
/** 节流 */
const dbLoadMore = debounce(async () => {
  let deadline = new Date()
  if (msgList.value.length > 0) {
    deadline = new Date(msgList.value[0].createdAt)
  }
  const historyMsg = buildMsg<HistoryBody>(globalToId.value, MsgType.History, {
    count: 10,
    deadline,
    msgList: []
  })
  socket.send(JSON.stringify(historyMsg))
}, 500)

const isDisconnected = ref(false)
const isFirstMsg = ref(false)
const isLoadingMore = ref(false)
const handleMsgsScroll = ({ scrollTop }) => {
  console.log('handleMsgsScroll:', scrollTop)
  if (scrollTop === 0) {
    loadMore()
  }
}

const handleUploadFile = async (
  options: UploadRequestOptions
): Promise<UploadRes> => {
  let file: File | Blob | UploadRawFile = options.file
  let fileTypeStr = options.file.type.split('/')[0]
  if (fileTypeStr === 'image') {
    const ext = (file as any).name.split('.').pop()
    // gif 不压缩，其它图片压缩再发送
    // file = await compressImage(options.file)
    if (ext !== 'gif') {
      file = await compressImage(options.file)
    }
  }
  if (
    fileTypeStr !== 'image' &&
    fileTypeStr !== 'video' &&
    fileTypeStr !== 'audio'
  ) {
    fileTypeStr = 'file'
  }
  const formData = {
    file,
    name: options.file.name,
    type: fileTypeStr as UploadData['type']
  }
  const { code, data } = await uploadFile(formData)
  if (code === HttpResultCode.Success) {
    return data
  } else {
    return Promise.reject('上传文件失败')
  }
}

const handleSendFile = (res: UploadRes) => {
  const type = (tp => {
    switch (tp) {
      case 1:
        return MsgType.Image
      case 2:
        return MsgType.Video
      case 3:
        return MsgType.Audio
      default:
        return MsgType.File
    }
  })(res.type)
  const msg = buildMsg<FileBody>(globalToId.value, type, {
    name: res.name,
    url: res.url
  })
  commonSend(msg)
}

function showTarName(id: number) {
  if (id === 0) return ''
  let user = userList.value.find(u => u.id === id)
  if (!user) {
    getOnUser(id).then(({ code, data }) => {
      if (code === HttpResultCode.Success) {
        userList.value.push(data)
        user = data
      }
    })
  }
  return user?.nickname || '未知用户'
}

function showTarAvatar(id: number) {
  const user = userList.value.find(u => u.id === id)
  return user?.avatar || ''
}

function sortConversationList(list: Conversation<any>[]) {
  conversationList.value = list.sort((a, b) => {
    if (a.top === b.top) {
      return (
        new Date(b.latestMsg.createdAt).getTime() -
        new Date(a.latestMsg.createdAt).getTime()
      )
    }
    return b.top - a.top
  })
  console.log('sortConversationList:', conversationList.value)
}

function procMsg(obj: Msg<any>) {
  console.log('procMsg:', obj)
  switch (obj.type) {
    case MsgType.Text:
    case MsgType.File:
    case MsgType.Image:
    case MsgType.Audio:
    case MsgType.Video:
      {
        const data = obj as Msg<TextBody>
        const list = conversationList.value
        if (data.fromId === globalToId.value) {
          // 如果是当前会话，更新消息列表
          msgList.value.push(data)
          // 更新会话列表
          list.map(c => {
            if (c.targetId === data.fromId) {
              c.latestMsg = data
            }
            return c
          })
          nextTick().then(() => {
            refMsgScroll.value.setScrollTop(999999)
          })
        } else {
          // 如果不是当前会话
          if (!list.find(c => c.targetId === data.fromId)) {
            // 如果会话列表中不存在该会话,则添加该会话
            const conversation: Conversation<any> = {
              userId: ownerId,
              type: 0,
              targetId: data.fromId,
              latestMsg: data,
              unread: 1,
              top: 0
            }
            list.unshift(conversation)
          } else {
            // 如果会话列表中存在该会话,则更新该会话,未读消息+1，并将该会话置顶
            let curIndex = -1
            list.forEach((c, index) => {
              if (c.targetId === data.fromId) {
                c.latestMsg = data
                c.unread = (c.unread || 0) + 1
                curIndex = index
              }
            })
            // if (curIndex > 0 && curIndex < list.length) {
            //   const cur = list.splice(curIndex, 1)
            //   list.unshift(cur[0])
            // }
          }
        }
        // conversationList.value = list
        sortConversationList(list)
      }
      break
    case MsgType.ConversationList:
      {
        const data = obj as Msg<ConversationBody>
        const { count, conversationList: list } = data.body
        if (count === 0) {
          return
        }
        sortConversationList(list)
      }
      break
    case MsgType.History:
      {
        isLoadingMore.value = false
        const data = obj as Msg<HistoryBody>
        const { count, msgList: list } = data.body
        if (count === 0) {
          isFirstMsg.value = true
          return
        }
        msgList.value = list.reverse().concat(msgList.value)
        nextTick().then(() => {
          // 当wrapRef的高度大于它第一个子元素，并且isFirstMsg为false时再触发一次加载更多
          // 这是用来解决当Item不多，滚动条不显示时无法触发加载更多的事件的问题
          const wrapRef = refMsgScroll.value.wrapRef
          const children = wrapRef.children
          if (children.length > 0) {
            const firstChild = children[0]
            if (wrapRef.clientHeight > firstChild.clientHeight) {
              loadMore()
              return
            }
          }
          // 查找第count个.msg-msg-view的底部距离
          let scrollHeight = 20
          if (count > 0 && document.querySelectorAll('.msg-view')[count - 1]) {
            const ele0 = document.querySelectorAll('.msg-view')[0]
            const ele = document.querySelectorAll('.msg-view')[count - 1]
            scrollHeight =
              ele.getBoundingClientRect().bottom -
              ele0.getBoundingClientRect().top
            if (scrollHeight < 20) {
              scrollHeight = 20
            }
          }
          if (refMsgScroll.value) {
            refMsgScroll.value.setScrollTop(scrollHeight)
          }
        })
      }
      break
    case MsgType.Subscribe:
      {
        const data = obj as Msg<SubscribeBody>
        if (obj.fromId === ownerId && obj.toId === globalToId.value) {
          onlineStatus.value = data.body.online
        }
      }
      break
    case MsgType.System: {
      const data = obj as Msg<SystemBody>
      const { event, eventCode } = data.body
      if (eventCode === EventCode.Disconnect) {
        message(event, { type: 'error' })
        isDisconnected.value = true
      }
    }
  }
}
const globalToId = ref(0)
const handleSelectConversation = (toId: number) => {
  console.log('handleSelectConversation:', ownerId)
  if (isDisconnected.value) {
    message('连接断开，请刷新', { type: 'error' })
    return
  }
  if (globalToId.value === toId) return
  onlineStatus.value = false
  isLoadingMore.value = false
  isFirstMsg.value = false
  globalToId.value = toId
  msgList.value = []
  conversationList.value = conversationList.value.map(c => {
    if (c.targetId === toId) {
      c.unread = 0
    }
    return c
  })
  loadMore()

  const subscribeMsg = buildMsg<SubscribeBody>(toId, MsgType.Subscribe, {
    online: true
  })
  socket.send(JSON.stringify(subscribeMsg))
}

function createConversationByUser(id: number) {
  // 查看是否已经存在会话
  const list = conversationList.value
  const conversation = list.find(c => c.targetId === id)
  if (conversation) {
    handleSelectConversation(id)
    return
  }
  const newConversation: Conversation<any> = {
    userId: ownerId,
    type: 0,
    targetId: id,
    latestMsg: null,
    unread: 0,
    top: 0
  }
  list.unshift(newConversation)
  sortConversationList(list)
  handleSelectConversation(id)
}

const handleCreateConversation = () => {
  addDialog({
    title: '创建会话',
    hideFooter: true,
    contentRenderer: ({ options, index }) => (
      <SelectUserView
        users={userList.value.filter(u => u.id !== ownerId)}
        onSelect={id => {
          closeDialog(options, index)
          createConversationByUser(id)
        }}
      />
    )
  })
}

const handleRemoveConversation = (id: number) => {
  const msg = buildMsg<SystemBody>(id, MsgType.System, {
    event: 'remove conversation',
    eventCode: EventCode.RemoveConversation
  })
  socket?.send(JSON.stringify(msg))
  conversationList.value = conversationList.value.filter(c => c.targetId !== id)
  if (globalToId.value === id) {
    globalToId.value = 0
  }
}

const handleSelectEmoji = (e: string) => {
  inputMsg.value += e
  refEmoji.value?.hide()
}

const handleTopConversation = (id: number, top: boolean) => {
  const msg = buildMsg<SystemBody>(id, MsgType.System, {
    event: top ? 'top conversation' : 'untop conversation',
    eventCode: top ? EventCode.TopConversation : EventCode.UnTopConversation
  })
  socket?.send(JSON.stringify(msg))
  conversationList.value = conversationList.value.map(c => {
    if (c.targetId === id) {
      c.top = top ? new Date().getTime() / 1000 : 0
    }
    return c
  })
  sortConversationList(conversationList.value)
}

onMounted(async () => {
  refreshData()
  // 这里需要获取所有用户列表，所以不需要分页
  const {
    code,
    data: { total, items }
  } = await getUserList({ page: 1, 'page-size': 9999999 })
  if (code === HttpResultCode.Success) {
    userList.value = items
  } else {
    return Promise.reject('获取用户列表失败')
  }
  socket = new WebSocket(
    import.meta.env.VITE_WS_URL + '/ws?token=' + getToken().accessToken
  )
  socket.onopen = function () {
    isDisconnected.value = false
    console.log('WebSocket open')
  }
  socket.onmessage = function (e) {
    console.log('WebSocket message', e.data)
    procMsg(JSON.parse(e.data))
  }
  socket.onclose = function () {
    console.log('WebSocket close')
    isDisconnected.value = true
    globalToId.value = 0
  }
  socket.onerror = function (e) {
    console.log('WebSocket error', e)
  }
})
onUnmounted(() => {
  socket?.close()
  socket = null
})
</script>
<style scoped lang="scss">
// .msg-input::v-deep(.el-textarea__inner) {
//   box-shadow: none;
// }
</style>
